////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#ifndef __WN_TYPE_MANAGER_H__
#define __WN_TYPE_MANAGER_H__

#include "WNCore/inc/WNBase.h"
#include "WNConcurrency/inc/WNCriticalSection.h"
#include "WNScripting/inc/WNScriptingConfig.h"
#include "WNScripting/inc/WNIdentifierMap.h"
#include "WNScripting/inc/WNTypeElement.h"
#include "WNFunction.h"

#include <list>

namespace llvm {
    class Value;
    class ConstantFolder;
    class GlobalVariable;
    class IRBuilderBase;
    class Type;
};

struct WNScriptVariable;
struct WNScriptingEngineData;
//TODO: Major cleanup on this file
//      it is pretty messy, need to move all of the "type" stuff at the very least
//      into it's own header
//TODO: Should probably hide the hash_map hashing/comparator somewhere else 
//      (probably a platform specific scripting header (hash_map is non standard)
//      (Worst case pass in VOID* and cast, do not like this.. figure out better way)

enum WNCastAllowance {
    eWNCastOk,
    eWNCastLossPrecision,
    eWNCastFail,
    eWNCastInvalidArithmetic
};

enum WNStaticArithmetic {
    eWNStaticAdd,
    eWNStaticSubtract,
    eWNStaticMultiply,
    eWNStaticDivide,
    eWNStaticAssign,
    eWNStaticEquality,
    eWNStaticBrackets,
    eWNStaticERROR
};

//This must line up with the sIntPredicates/sFloatPredicates
enum WNStaticPredicate {
    eWNEqual = 0,
    eWNNotEqual = 1,
    eWNLessThan = 2,
    eWNGreaterThan = 3,
    eWNLessThanOrEqual = 4,
    eWNGreaterThanOrEqual = 5,
    eWNInvalidPredicate = 255
};

struct WNNativeElementType {
    WNTypeElement mScriptType;
    WN_CHAR* mUserType;
    WN_CHAR mCharacterIdentifier;
    WN_BOOL mArray;
};

typedef WN_VOID (*WNStaticArithmeticFunction)(WN_CHAR*, WN_CHAR*, WNStaticArithmetic, WNStaticPredicate, WNTypeElement&, WNCastAllowance&, WN_CHAR*);
typedef WNCastAllowance (*WNVerifyDynamicArithmetic)(WNStaticArithmetic, const WNTypeElement&, const WNTypeElement&, WNTypeElement&);
typedef WN_BOOL  (*WNStaticTypeCheckFunction)(WN_CHAR*);
typedef llvm::Value* (*WNGenerateLLVMOperation)(llvm::Value*, llvm::Value*, const WNTypeElement&, WNStaticArithmetic, WNStaticPredicate, WNScriptingEngineData&);
typedef llvm::Value* (*WNGenerateLLVMCast)(llvm::Value* val, llvm::IRBuilderBase&);
typedef llvm::Value* (*WNCreateConstantFunction)(WNScriptingEngineData&, WN_CHAR* constValue);
typedef llvm::Value* (*WNCreateStore)(WNScriptingEngineData&, llvm::Value*, llvm::Value*, bool initial);
typedef llvm::Value* (*WNGetCopySize)(WNScriptingEngineData&, llvm::Value* val);

template <>
__WN_INLINE WN_BOOL WNIsValid( WNTypeElement _value ) {
    return( _value != WN_NULL );
}

struct WNArithmeticStruct {
    WNTypeElement mLHS;
    WNTypeElement mRHS;
    WNStaticArithmeticFunction mStaticArithmetic;
    WNVerifyDynamicArithmetic  mVerifyDynamicArithmetic;
    WNGenerateLLVMOperation    mGenerateLLVMOperation;
    WNGenerateLLVMCast         mGenerateCast;
};

struct WNTypeCheckStruct {
    WNTypeElement type;
    WNStaticTypeCheckFunction mStaticTypeCheck;
    WNCreateConstantFunction  mCreateConstant;
    WNCreateStore             mCreateStore;
    WNGetCopySize             mGetCopySize;
};

struct __WNTypeLess {
    WN_BOOL operator()(WNTypeElement _key1, WNTypeElement _key2) const {
        return (((*_key1).mUserTypeName < (*_key2).mUserTypeName) ||
                ((*_key1).mUserTypeName == (*_key2).mUserTypeName &&
                 (*_key1).mSubType < (*_key2).mSubType));
    }
};

typedef std::list<WNTypeElement> WNArrayHashMap;
typedef std::list<WNArithmeticStruct*> WNArithmeticStructList;
typedef std::list<WNTypeCheckStruct*> WNTypeCheckStructList;
typedef std::list<WNNativeElementType*> WNNativeElementList;
typedef std::list<WNTypeElement> WNTypeElementList;

const WNTypeElement WNInvalidType = WN_NULL;

template <WNTypeElement>
__WN_INLINE WN_BOOL WNIsValid( WNTypeElement _value ) {
    return ( _value != WNInvalidType );
}

class WNTypeManager {
public:
    WNTypeManager(WNIdentifierMap& _identifierMap);
    WN_BOOL TypeExists(WNTypeElement _type);
    WNTypeElement GetType(WNVariableType _type, WNIdentifierElement _name = WNInvalidIdentifier);
    WNTypeElement AddUserType(WNIdentifierElement _type);
    WNIdentifierElement GetUserTypeName(WNTypeElement _userType);
    WNTypeElement GetUserTypeByName(WNIdentifierElement _name);
    WNTypeElement GetArrayType(WNTypeElement _arrayType);
    WN_VOID AddStructBody(WNTypeElement _elem, const std::vector<WNTypeElement>& _typeList, const std::vector<WNIdentifierElement>& _argumentList);
    WNTypeElement RegisterStructType(WNIdentifierElement _structName);

    //TODO: Look at the system, and see if this is really the case (that we get and only use once)
    //      Since if we ever use twice, we can save the lookup time by keeping it like this
    WNStaticArithmeticFunction GetStaticArithmeticFunction(WNTypeElement _arg1, WNTypeElement _arg2);
    WNStaticTypeCheckFunction GetStaticTypeCheckFunction(WNTypeElement _element);
    WNVerifyDynamicArithmetic GetDynamicArithmeticVerification(WNTypeElement _to, WNTypeElement _from);
    WNGenerateLLVMOperation   GetLLVMOperationGenerator(WNTypeElement _arg1, WNTypeElement _arg2);
    WNCreateConstantFunction  GetLLVMCreateConstant(WNTypeElement _arg);
    WNCreateStore       GetLLVMStoreInst(WNTypeElement _arg);
    WNGenerateLLVMCast  GetLLVMCastGenerator(WNTypeElement _to, WNTypeElement _from);
    WNGetCopySize       GetLLVMCopySize(WNTypeElement _arg);
    WNNativeElementType* GetNativeFromType(WNTypeElement _type);
    WNNativeElementType* GetNativeFromWNType(WNIdentifierElement _nativeType);
    WNNativeElementType* GetNativeArrayFromWNType(WNIdentifierElement _nativeType);
    llvm::Value* GenerateLLVMAllocation(WN_BOOL _heap, WNScriptingEngineData &_data, llvm::Type* _type, llvm::Value* _numElems = WN_NULL);

    llvm::Type* GetIntPtrType() const;
    llvm::Type* GetLLVMType(WNTypeElement _type) const;
    WN_BOOL IsArrayType(WNTypeElement _type) const;
    WN_BOOL IsReferenceType(WNTypeElement _type) const;
    WN_BOOL IsStructType(WNTypeElement _structType) const;
    WNTypeElement GetSubType(WNTypeElement _type) const;
    WN_VOID Initialize(const WNScriptingEngineData& _initializationData);
    WN_BOOL GetStructValueLocation(WNTypeElement _elem, WNIdentifierElement _ident, WNTypeElement& _retElem, WN_SIZE_T& _outVal);
    llvm::Value* CreateAllocation(WNScriptingEngineData &_data, WNTypeElement _type, const std::vector<llvm::Value*>& _amounts, WN_BOOL _heap, WN_BOOL _suppressLastCreation);

    
    llvm::Value* CreateConstAtomicAdd(WNScriptingEngineData& _data, llvm::Value* _ptr, WN_SIZE_T _addAmount);
    llvm::Value* CreateConstAtomicSubtract(WNScriptingEngineData& data, llvm::Value* _ptr, WN_SIZE_T subAmount);
    std::vector<llvm::GlobalVariable*> mGlobalVariables;
    llvm::StructType* mInternalStringStruct;

private:
    llvm::Value* RecurseCreateArrayAlloc(WNScriptingEngineData& _data, WNTypeElement _type, const std::vector<llvm::Value*>& _amounts, WN_BOOL _heap, WN_SIZE_T _arrayIndex, WN_BOOL _suppressLastCreation);

    WNArithmeticStruct* GetStaticArithmeticStruct(WNTypeElement, WNTypeElement);
    WNTypeCheckStruct*  GetStaticTypeCheckStruct(WNTypeElement);
    WNTypeElementList mTypeMap;
    WNArrayHashMap mArrayHashMap;
    WNTypeCheckStructList mTypeCheckStructList;
    WNArithmeticStructList mArithmeticStructList;
    WNNativeElementList mNativeTypeList;
    WN_BOOL mInitializing;
    WNIdentifierMap& mIdentifierMap;
    ///LLVM SPECIFIC STUFF
    llvm::Type* mPointerInteger;

    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        WN_CRITICAL_SECTION_T mTypeManagerLock;
        WN_CRITICAL_SECTION_T mTypeCheckLock;
        WN_CRITICAL_SECTION_T mArithmeticLock;
    #endif
};

#endif // __WN_TYPE_MANAGER_H__